package cn.com.bs.srmp.runner;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.lang3.StringUtils;

import com.adrninistrator.javacg.common.JavaCGConstants;
import com.adrninistrator.javacg.stat.JCallGraph;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import cn.com.bs.srmp.common.Constants;
import cn.com.bs.srmp.conf.PluginContext;
import cn.com.bs.srmp.dto.entity.AnnotationInfo;
import cn.com.bs.srmp.dto.entity.ExternalInterfaceInfo;
import cn.com.bs.srmp.dto.entity.FeignClientInfo;
import cn.com.bs.srmp.dto.entity.InterfaceInfo;
import cn.com.bs.srmp.dto.entity.MethodInfo;
import cn.com.bs.srmp.dto.excel.ExternalInterfaceExcel;
import cn.com.bs.srmp.dto.excel.InterfaceExcel;
import cn.com.bs.srmp.util.PluginUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * @Author mic
 * @Date 2022-07-27 16:03
 */
@Slf4j
public class RunnerWriteFile extends AbstractRunner {

    /**
     * feign接口信息
     */
    private List<ExternalInterfaceInfo> externalFeignInterfaceList = new ArrayList<>();
    /**
     * annotationInfoMap
     */
    private Map<String, AnnotationInfo> annotationInfoMap = new HashMap<>();
    /**
     * 当前项目继承 feign接口全路径
     */
    private Set<String> feignClientInterfaceSet = new HashSet<>();
    /**
     * 接口 FeignClient 注解关联关系
     */
    private Map<String, FeignClientInfo> feignClientMap = new HashMap<>();
    /**
     * 当前项目feign接口
     */
    private List<InterfaceInfo> feignInterfaceList = new ArrayList<>();
    /**
     * feign被调用
     */
    private Map<String, Set<MethodInfo>> feignCalleeMethodMap = new HashMap<>();
    /**
     * zip path
     */
    private String zipPath;
    /**
     * dependJarPath path
     */
    private String dependJarPath;
    /**
     * Java方法调用关系输出文件路径
     */
    private String callGraphOutputFilePath = null;
    /**
     * 注解相关内容输出文件路径
     */
    private String callGraphAnnotationOutputFilePath = null;
    /**
     * 方法代码行号输出文件路径
     */
    private String callGraphLineNumberOutputFilePath = null;


    public RunnerWriteFile(PluginContext pluginContext) {
        this.pluginContext = pluginContext;
    }


    @Override
    public boolean init() {
        if (!PluginUtil.isCollectionEmpty(pluginContext.getFeignApiPaths())) {
            //进行mvn package 打包
            this.packageApi();
            //获取当前项目提供的feign包路径
            this.processFeignPackage();
        } else {
            log.info("FeignApiPaths is null");
        }
        Set<String> excludeModuleFuzzyNames = pluginContext.getExcludeModuleFuzzyNames();
        if (PluginUtil.isCollectionEmpty(excludeModuleFuzzyNames)) {
            excludeModuleFuzzyNames = new HashSet<>();
        }
        excludeModuleFuzzyNames.add(Constants.JAR_FILTER_POJO);
        pluginContext.setExcludeModuleFuzzyNames(excludeModuleFuzzyNames);
        //扫描主路径获取
        String scanPath = this.getScanPath(pluginContext.getProjectPath());

        //扫描jar zip
        File projectFile = new File(scanPath);
        if (!projectFile.exists()) {
            log.error("projectPath 路径不存在:{}", projectFile.getAbsolutePath());
            return false;
        }
        File[] files = projectFile.listFiles();
        Set<String> jarPaths = new HashSet<>();

        for (File file : files) {
            jarPaths.addAll(getJarPath(file));
        }
        this.filterModule(jarPaths);
        if (StringUtils.isEmpty(pluginContext.getFileOutputPath())) {
            pluginContext.setFileOutputPath(pluginContext.getProjectPath() + "/init/configData/");
        }
        File file = new File(pluginContext.getFileOutputPath());
        if (!file.exists()) {
            file.mkdirs();
        }
        //拉取当前项目依赖
        this.dependCopy(scanPath);
        //扫描依赖的jar加入到合并jar列表中
        this.scanDependJar();
        return true;
    }

    /**
     * 扫描依赖jar
     */
    private void scanDependJar() {
        if (StringUtils.isEmpty(dependJarPath)) {
            log.error("dependDir is null");
            return;
        }
        File dependDir = new File(dependJarPath);
        if (dependDir == null || !dependDir.exists()) {
            log.error("dependDirFile is null");
            return;
        }
        File[] files = dependDir.listFiles();
        if (files == null || files.length == 0) {
            log.error("dependDir File is null");
            return;
        }
        Set<String> dependJarPaths = new HashSet<>();
        for (File file : files) {
            if (file.getName().contains("api")
                    && !file.getName().contains("slf4j")
                    && !file.getName().contains("log4j")
                    && !file.getName().contains("maven")
                    && !file.getName().contains("github")
                    && !file.getName().contains("nacos")
                    && !file.getName().contains("jakarta")
                    && !file.getName().contains("nacos")
                    && !file.getName().contains("javax")
                    && !file.getName().contains("stax")
                    && !file.getName().contains("junit")
                    && !file.getName().contains("apache")
                    && !file.getName().contains("spring")) {
                dependJarPaths.add(file.getAbsolutePath());
            }
        }
        pluginContext.getJarPaths().addAll(dependJarPaths);
    }

    /**
     * 根据FeignApi 绝对路径扫描src下java文件处理
     */
    public void processFeignPackage() {
        for (String feignApiPath : pluginContext.getFeignApiPaths()) {
            File feignApiPathFile = new File(feignApiPath);
            if (!feignApiPathFile.exists()) {
                continue;
            }
            File srcFile = Stream.of(feignApiPathFile.listFiles())
                    .filter(file -> file.getName().equals(Constants.SRC))
                    .findFirst()
                    .orElse(null);
            if (srcFile == null) {
                continue;
            }
            processScanJava(srcFile);
        }
    }

    /**
     * 循环扫描java文件处理
     *
     * @param file
     */
    public void processScanJava(File file) {
        File[] files = file.listFiles();
        if (files == null || files.length == 0) {
            return;
        }
        for (File subFile : files) {
            if (subFile.getName().endsWith(Constants.JAVA_SUFFIX)) {
                readJavaPackage(subFile);
                continue;
            }
            processScanJava(subFile);
        }
    }

    /**
     * 读取java文件的package路径
     *
     * @param file
     */
    public void readJavaPackage(File file) {
        InputStreamReader read = null;
        try {
            read = new InputStreamReader(new FileInputStream(file), "UTF-8");
            BufferedReader br = new BufferedReader(read);//BufferedReader读取文件
            String s = null;
            String packageContext = null;
            int i = 0;
            while ((s = br.readLine()) != null) {
                i++;
                if (i > Constants.SCAN_LINES) {
                    break;
                }
                if (StringUtils.isNotEmpty(s) && s.startsWith("package ")) {
                    packageContext = s;
                }
                if (StringUtils.isNotEmpty(s) && s.contains(" interface ")
                        && StringUtils.isNotEmpty(packageContext)) {
                    Set<String> feignReferencePaths = pluginContext.getFeignReferencePaths();
                    if (feignReferencePaths == null) {
                        feignReferencePaths = new HashSet<>();
                    }
                    String path = StringUtils.substringAfter(packageContext, "package ");
                    path = StringUtils.substringBefore(path, ";");
                    feignReferencePaths.add(path.trim());
                    pluginContext.setFeignReferencePaths(feignReferencePaths);
                    break;
                }

            }
        } catch (Exception e) {
            log.error("error:", e);
            e.printStackTrace();
        }

    }

    public String getScanPath(String path) {
        File pathFile = new File(path);
        File[] files = pathFile.listFiles();
        File pomFile = Stream.of(files).filter(file ->
                file.getName().equals("pom.xml")
        ).findFirst().orElse(null);
        if (pomFile == null) {
            log.error("pomFile is null path:{}", path);
        }
        String parentDirectory = PluginUtil.getParentDirectory(path);
        File parentDirectoryFile = new File(parentDirectory);
        File parentDirectoryPomFile = Stream.of(parentDirectoryFile.listFiles()).filter(file ->
                file.getName().equals("pom.xml")
        ).findFirst().orElse(null);
        if (parentDirectoryPomFile != null) {
            return parentDirectory;
        }
        return path;
    }


    public void packageApi() {
        for (String feignPath : pluginContext.getFeignApiPaths()) {
            File wd = new File("/bin");
            System.out.println(wd);
            Process proc = null;

            try {
                proc = Runtime.getRuntime().exec("/bin/bash", null, wd);

            } catch (IOException e) {
                e.printStackTrace();

            }
            if (proc != null) {
                BufferedReader in = new BufferedReader(new InputStreamReader(proc.getInputStream()));

                PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(proc.getOutputStream())), true);

                out.println("cd " + feignPath); //执行该语句后返回上一级目录

                out.println("pwd"); //打印当前目录

                out.println("mvn -Dmaven.test.skip=true clean package");//执行该目录下的jar文件,Linux下执行jar文件必须进入其所在的文件夹

                out.println("exit");

                try {
                    String line;

                    while ((line = in.readLine()) != null) {
                        System.out.println(line);

                    }

                    proc.waitFor();
                    in.close();

                    out.close();

                    proc.destroy();

                } catch (Exception e) {
                    e.printStackTrace();
                    log.error("package error : ", e);
                }
            }
        }

    }

    /**
     * 拉取项目依赖
     *
     * @param path
     */
    public void dependCopy(String path) {
        File wd = new File("/bin");
        Process proc = null;
        try {
            proc = Runtime.getRuntime().exec("/bin/bash", null, wd);
        } catch (IOException e) {
            log.error("error:", e);
            e.printStackTrace();
        }
        dependJarPath = PluginUtil.getParentDirectory(zipPath) + "/dependJar";
        File dependJarDir = new File(dependJarPath);
        if (!dependJarDir.exists()) {
            dependJarDir.mkdirs();
        }
        if (proc != null) {
            BufferedReader in = new BufferedReader(new InputStreamReader(proc.getInputStream()));

            PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(proc.getOutputStream())), true);

            out.println("cd " + path); //执行该语句后返回上一级目录

            out.println("pwd"); //打印当前目录

            out.println("mvn dependency:copy-dependencies -DoutputDirectory=" + dependJarPath + " -DincludeScope=compile");//执行该目录下的jar文件,Linux下执行jar文件必须进入其所在的文件夹

            out.println("exit");

            try {
                String line;

                while ((line = in.readLine()) != null) {
                    System.out.println(line);
                }

                proc.waitFor();
                in.close();

                out.close();

                proc.destroy();

            } catch (Exception e) {
                e.printStackTrace();
                log.error("package error : ", e);
            }
        }
    }

    @Override
    public boolean check() {
        if (pluginContext == null) {
            log.error("{} 配置为空", this.getClass().getSimpleName());
            return false;
        }
        if (StringUtils.isEmpty(pluginContext.getComponentName())) {
            log.error("{} componentName为空", this.getClass().getSimpleName());
            return false;
        }
        if (StringUtils.isEmpty(pluginContext.getProjectPath())) {
            log.error("{} projectPath为空", this.getClass().getSimpleName());
            return false;
        }
        return true;
    }

    private void filterModule(Set<String> jarPaths) {
        if (!PluginUtil.isCollectionEmpty(pluginContext.getExcludeModuleFuzzyNames())) {
            jarPaths.removeIf(jarPath -> {
                boolean result = false;
                for (String exclude : pluginContext.getExcludeModuleFuzzyNames()) {
                    if (jarPath.contains(exclude)) {
                        result = true;
                    }
                }
                return result;
            });
        }
        if (!PluginUtil.isCollectionEmpty(pluginContext.getSpecifyModuleFuzzyNames())) {
            jarPaths.removeIf(jarPath -> {
                boolean result = true;
                for (String specify : pluginContext.getSpecifyModuleFuzzyNames()) {
                    if (jarPath.contains(specify)) {
                        result = false;
                    }
                }
                return result;
            });
        }
        log.info("jarPaths:{}", JSON.toJSONString(jarPaths));
        pluginContext.setJarPaths(jarPaths);
    }

    @Override
    public void operate() {
        //过滤包 并获取feign接口的路径
        doFeignOperate();
        //获取feign接口的被调用情况
        doOperate();
        //生成excel
        doWriteExcel();
        //生成html
        doWriteHtml();
        //生成文件放入zip
        doMergeToZip();
        //删除合并文件
        doDelete();
    }

    private void doMergeToZip() {
        if (StringUtils.isEmpty(zipPath)) {
            log.error("zipPath is null");
            return;
        }
        //获取包父类路径
        String zipParentPath = PluginUtil.getParentDirectory(zipPath);
        log.info("zipParentPath:{}", zipParentPath);
        //获取解压后路径
        String decompressionPath = StringUtils.substringBefore(zipPath, ".zip");
        log.info("decompressionPath:{}", decompressionPath);
        //文件输出目录
        File fileOutFile = new File(pluginContext.getFileOutputPath());
        log.info("fileOutFile:{}", fileOutFile);
        //文件输出路径截取
        String outPutPathSuffix = StringUtils.substringAfter(pluginContext.getFileOutputPath(), pluginContext.getProjectPath());
        log.info("outPutPathSuffix:{}", outPutPathSuffix);
        //解压文件
//        ZipUtil.unzip(new File(zipPath), zipParentPath);
        try {
//            FileUtils.copyDirectory(fileOutFile, new File(decompressionPath + "/" + outPutPathSuffix));

            //压缩文件
//            ZipUtil.zip(Arrays.asList(decompressionPath), zipPath);
//            File decompressionFile = new File(decompressionPath);
//            FileUtils.deleteDirectory(decompressionFile);

        } catch (Exception e) {
            log.error("error: ", e);
            e.printStackTrace();
        }
    }

    /**
     * 根据处理的结果 生成excel
     */
    private void doWriteExcel() {
        ExcelWriter excelWriter = EasyExcel.write(pluginContext.getFileOutputPath() + Constants.FLAG_SLASH + Constants.EXCEL_NAME).build();
        List<ExternalInterfaceExcel> externalInterfaceExcels = new ArrayList<>(500);
        for (Map.Entry<String, Set<MethodInfo>> entry : feignCalleeMethodMap.entrySet()) {
            Set<MethodInfo> methodInfos = entry.getValue();
            methodInfos.forEach(methodInfo -> {
                ExternalInterfaceExcel externalInterfaceExcel = ExternalInterfaceExcel.builder()
                        .componentName(pluginContext.getComponentName())
                        .relyComponentName(methodInfo.getProject())
                        .relyType(Constants.RELY_TYPE)
                        .method(methodInfo.getFeignFullName() + Constants.FLAG_COLON + methodInfo.getName() + methodInfo.getParams())
                        .fuse(methodInfo.getFuse() ? Constants.YES : Constants.NO)
                        .mappingPath(methodInfo.getMappingValue())
                        .desc(methodInfo.getApiOperationValue())
                        .build();
                externalInterfaceExcels.add(externalInterfaceExcel);
            });
        }
        WriteSheet externalInterfaceSheet = EasyExcel.writerSheet(pluginContext.getComponentName() + Constants.RELY_SHEET_SUFFIX)
                .head(ExternalInterfaceExcel.class)
                .build();
        excelWriter.write(externalInterfaceExcels, externalInterfaceSheet);

        List<InterfaceExcel> interfaceExcels = new ArrayList<>(500);
        for (InterfaceInfo interfaceInfo : feignInterfaceList) {
            InterfaceExcel interfaceExcel = InterfaceExcel.builder()
                    .componentName(pluginContext.getComponentName())
                    .relyType(Constants.RELY_TYPE)
                    .mappingPath(interfaceInfo.getMappingPath())
                    .desc(interfaceInfo.getDesc())
                    .method(interfaceInfo.getFullName())
                    .build();
            interfaceExcels.add(interfaceExcel);
        }
        WriteSheet interfaceSheet = EasyExcel.writerSheet(pluginContext.getComponentName() + Constants.SUPPLY_SHEET_SUFFIX)
                .head(InterfaceExcel.class)
                .build();
        excelWriter.write(interfaceExcels, interfaceSheet);
        excelWriter.finish();
    }

    /**
     * 根据处理信息生成html,js
     */
    private void doWriteHtml() {
        String html = Constants.HTML;
        try {

            File htmlFile = new File(pluginContext.getFileOutputPath() + Constants.FLAG_SLASH + Constants.HTML_NAME);
            if (!htmlFile.exists()) {
                htmlFile.createNewFile();
            }
            FileOutputStream fileOutputStream = new FileOutputStream(htmlFile);
            PrintStream printStream = new PrintStream(fileOutputStream);
            printStream.println(html);
            JSONObject json = new JSONObject();
            Set<String> projects = externalFeignInterfaceList.stream().map(ExternalInterfaceInfo::getProject).collect(Collectors.toSet());
            projects.add(pluginContext.getComponentName());
            List<JSONObject> nodes = new ArrayList<>();
            projects.forEach(project -> {
                JSONObject node = new JSONObject();
                node.put("data", new JSONObject().fluentPut("id", project).fluentPut("name", project));
                nodes.add(node);
            });
            json.put("nodes", nodes);

            List<JSONObject> edges = new ArrayList<>(500);

            for (Map.Entry<String, Set<MethodInfo>> entry : feignCalleeMethodMap.entrySet()) {
                Set<MethodInfo> methodInfos = entry.getValue();
                for (MethodInfo methodInfo : methodInfos) {
                    JSONObject edge = new JSONObject();
                    edge.put("data", new JSONObject().fluentPut("source", pluginContext.getComponentName()).fluentPut("target", methodInfo.getProject())
                            .fluentPut("label", methodInfo.getFeignFullName() + Constants.FLAG_COLON + methodInfo.getName()));
                    edges.add(edge);
                }
            }
            json.put("edges", edges);

            printStream.println("<script> var webkitDep = " + json.toJSONString() + "</script>");
            fileOutputStream.flush();
            printStream.flush();
            fileOutputStream.close();
            printStream.close();
        } catch (Exception e) {
            log.error("error:", e);
        }
    }

    private boolean doFeignOperate() {
        // 判断是否需要调用java-callgraph2生成jar包的方法调用关系
        if (!callJavaCallGraph()) {
            return false;
        }
        // 处理外部feign接口的注解信息
        if (!handleExternalFeignMethodAnnotation()) {
            return false;
        }
        //处理接口信息
        if (!handleExternalFeignInfo()) {
            return false;
        }
        //特殊处理
        specialHandleExternalFeignInfo();
        return true;
    }


    private boolean doOperate() {
        // 处理通过java-callgraph2生成的方法调用关系文件
        if (!handleMethodCall()) {
            return false;
        }
        // 处理当前项目对外提供的feign接口
        if (!handleFeignInfo()) {
            return false;
        }
        log.info("feign被调用关系链 {}", JSON.toJSONString(feignCalleeMethodMap));
        return true;
    }


    /**
     * 调用java-callgraph2生成jar包的方法调用关系
     *
     * @return
     */
    private boolean callJavaCallGraph() {
        log.info("尝试调用java-callgraph2生成jar包的方法调用关系 {}", pluginContext.getJarPaths());

        String[] array = getJarArray();
        for (String jarName : array) {
            if (!new File(jarName).exists()) {
                log.error("文件或目录不存在 {}", jarName);
                return false;
            }
        }

        // 调用java-callgraph2
        JCallGraph jCallGraph = new JCallGraph();

        boolean success = jCallGraph.run(array);
        if (!success) {
            log.error("调用java-callgraph2生成jar包的方法调用关系失败");
            return false;
        }

        // 记录Java方法调用关系输出文件路径
        callGraphOutputFilePath = jCallGraph.getOutputFilePath();
        // 记录注解相关内容输出文件路径
        callGraphAnnotationOutputFilePath = jCallGraph.getAnnotationOutputFilePath();
        // 记录方法代码行号输出文件路径
        callGraphLineNumberOutputFilePath = jCallGraph.getMethodLineNumberOutputFilePath();
        return true;
    }


    /**
     * 处理通过java-callgraph2生成的方法调用关系文件
     *
     * @return
     */
    private boolean handleMethodCall() {
        try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(callGraphOutputFilePath), StandardCharsets.UTF_8))) {
            String line;
            while ((line = br.readLine()) != null) {
                if (StringUtils.isBlank(line)) {
                    continue;
                }

                if (line.startsWith(JavaCGConstants.FILE_KEY_METHOD_PREFIX)) {
                    // 处理一条方法调用
                    if (!handleOneMethodCall(line)) {
                        return false;
                    }
                }
            }
            return true;
        } catch (Exception e) {
            log.error("error ", e);
            return false;
        }
    }

    /**
     * 特殊处理 自己调用自己的feign
     *
     * @return
     */
    private void specialHandleExternalFeignInfo() {
        Set<String> externalFullNames = externalFeignInterfaceList.stream().map(ExternalInterfaceInfo::getFullName)
                .collect(Collectors.toSet());
        List<String> notExistNames = feignClientInterfaceSet.stream().
                filter(externalFeignClientInterface ->
                        !externalFullNames.contains(externalFeignClientInterface)
                ).collect(Collectors.toList());

        if (PluginUtil.isCollectionEmpty(notExistNames)) {
            return;
        }
        for (String notExistName : notExistNames) {
            ExternalInterfaceInfo externalInterfaceInfo = ExternalInterfaceInfo.builder()
                    .fullName(notExistName)
                    .feignFullName(notExistName)
                    .desc("")
                    .build();
            FeignClientInfo feignClientInfo = feignClientMap.get(notExistName);
            if (feignClientInfo != null) {
                if (StringUtils.isNotEmpty(feignClientInfo.getName())) {
                    String project = feignClientInfo.getName().split(Constants.FLAG_COLON)[1];
                    externalInterfaceInfo.setProject(project.substring(0, project.length() - 1));
                }
                externalInterfaceInfo.setFallbackFactory(feignClientInfo.getFallbackFactory());
            }
            externalFeignInterfaceList.add(externalInterfaceInfo);
        }
    }

    /**
     * 读取关系文件缓存外部feign接口信息
     *
     * @return
     */
    private boolean handleExternalFeignInfo() {
        try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(callGraphOutputFilePath), StandardCharsets.UTF_8))) {
            String line;
            while ((line = br.readLine()) != null) {
                if (StringUtils.isBlank(line)) {
                    continue;
                }

                if (line.startsWith(JavaCGConstants.FILE_KEY_CLASS_PREFIX)) {
                    int indexBlank = line.indexOf(Constants.FLAG_SPACE);
                    String callerFullClassName = line.substring(Constants.FILE_KEY_PREFIX_LENGTH, indexBlank).trim();
                    String calleeFullClassName = line.substring(indexBlank + 1).trim();
                    //过滤在ExternalFeignReferencePaths包路径下的接口 排除兜底实现和工厂

                    feignClientInterfaceSet
                            .forEach(feignPackagePath -> {
                                if (callerFullClassName.equals(feignPackagePath)
                                ) {
                                    log.info("[{}] feignInterface [{}]", callerFullClassName, calleeFullClassName);
                                    ExternalInterfaceInfo externalInterfaceInfo = ExternalInterfaceInfo.builder()
                                            .fullName(callerFullClassName)
                                            .feignFullName(calleeFullClassName)
                                            .desc("")
                                            .build();
                                    FeignClientInfo feignClientInfo = feignClientMap.get(feignPackagePath);
                                    if (feignClientInfo != null) {
                                        if (StringUtils.isNotEmpty(feignClientInfo.getName())) {
                                            String project = feignClientInfo.getName().split(Constants.FLAG_COLON)[1];
                                            externalInterfaceInfo.setProject(project.substring(0, project.length() - 1));
                                        }
                                        externalInterfaceInfo.setFallbackFactory(feignClientInfo.getFallbackFactory());
                                    }
                                    externalFeignInterfaceList.add(externalInterfaceInfo);
                                }
                            });
                }
            }
            return true;
        } catch (Exception e) {
            log.error("handleExternalFeignInfo error ", e);
            return false;
        }
    }

    /**
     * 处理当前项目对外提供的feign接口
     *
     * @return
     */
    private boolean handleFeignInfo() {
        if (PluginUtil.isCollectionEmpty(pluginContext.getFeignReferencePaths())) {
            log.info("FeignReferencePaths is null");
            return true;
        }
        try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(callGraphAnnotationOutputFilePath), StandardCharsets.UTF_8))) {
            String line;
            while ((line = br.readLine()) != null) {
                if (StringUtils.isBlank(line)) {
                    continue;
                }
                if (line.startsWith(JavaCGConstants.FILE_KEY_METHOD_PREFIX)) {
                    this.handleFeignAnnotationInfo(line);
                }
            }
            return true;
        } catch (Exception e) {
            log.error("error ", e);
            return false;
        }
    }

    /**
     * 处理一条方法调用
     *
     * @param line
     * @return
     */
    private boolean handleOneMethodCall(String line) {
        String[] methodCallArray = line.split(Constants.FLAG_SPACE);
        //如果不是在feign包下的
        if (feignClientInterfaceSet.stream().filter(e -> line.contains(e)).collect(Collectors.toList()).isEmpty()) {
            return true;
        }

        if (methodCallArray.length != 5) {
            log.error("方法调用信息非法 [{}] [{}]", line, methodCallArray.length);
            return false;
        }
        String calleeFullMethod = methodCallArray[2];

        int indexCalleeRightBracket = calleeFullMethod.indexOf(Constants.FLAG_RIGHT_BRACKET);

        String finalCalleeFullMethod = calleeFullMethod.substring(indexCalleeRightBracket + 1).trim();

        String calleeMethodName = PluginUtil.getOnlyMethodName(finalCalleeFullMethod);

        String calleeFullClassName = PluginUtil.getFullClassNameFromMethod(finalCalleeFullMethod);

        String params = PluginUtil.getParamsFromMethod(calleeFullMethod);
        if (feignClientInterfaceSet.contains(calleeFullClassName)) {
            ExternalInterfaceInfo projectInter = externalFeignInterfaceList.stream()
                    .filter(f -> Objects.equals(f.getFullName(), calleeFullClassName))
                    .findFirst()
                    .orElse(ExternalInterfaceInfo.builder().build());
            String project = "";
            if (projectInter != null) {
                project = projectInter.getProject();
            }
            AnnotationInfo annotationInfo = annotationInfoMap.get(projectInter.getFeignFullName() + Constants.FLAG_COLON + calleeMethodName + params);

            if (feignCalleeMethodMap.containsKey(project)) {
                Set<MethodInfo> methodInfos = feignCalleeMethodMap.get(project);
                MethodInfo methodInfo = MethodInfo.builder()
                        .fullName(calleeFullClassName)
                        .feignFullName(projectInter.getFeignFullName())
                        .name(calleeMethodName)
                        .project(project)
                        .fuse(StringUtils.isNotBlank(projectInter.getFallbackFactory()))
                        .params(params)
                        .build();
                if (annotationInfo != null) {
                    methodInfo.setApiOperationValue(annotationInfo.getApiOperationValue());
                    methodInfo.setMappingValue(annotationInfo.getMappingValue());
                }
                methodInfos.add(methodInfo);
            } else {
                Set<MethodInfo> methodInfos = new HashSet<>();
                MethodInfo methodInfo = MethodInfo.builder()
                        .fullName(calleeFullClassName)
                        .feignFullName(projectInter.getFeignFullName())
                        .name(calleeMethodName)
                        .project(project)
                        .fuse(StringUtils.isNotBlank(projectInter.getFallbackFactory()))
                        .params(params)
                        .build();
                methodInfos.add(methodInfo);
                if (annotationInfo != null) {
                    methodInfo.setApiOperationValue(annotationInfo.getApiOperationValue());
                    methodInfo.setMappingValue(annotationInfo.getMappingValue());
                }
                feignCalleeMethodMap.put(project, methodInfos);
            }
        }
        return true;
    }


    /**
     * 处理外部feign接口注解FeignClient信息
     *
     * @return
     */
    private boolean handleExternalFeignMethodAnnotation() {
        try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(callGraphAnnotationOutputFilePath), StandardCharsets.UTF_8))) {
            String line;
            while ((line = br.readLine()) != null) {
                if (StringUtils.isBlank(line)) {
                    continue;
                }
                if (line.startsWith(JavaCGConstants.FILE_KEY_CLASS_PREFIX)) {
                    // 处理一个类注解信息
                    if (!handleClassFeignAnnotationInfo(line)) {
                        return false;
                    }
                } else if (line.startsWith(JavaCGConstants.FILE_KEY_METHOD_PREFIX)) {
                    // 处理一个方法注解信息
                    if (!handleMethodFeignAnnotationInfo(line)) {
                        return false;
                    }
                }
            }
            return true;
        } catch (Exception e) {
            log.error("error ", e);
            return false;
        }
    }


    /**
     * 处理外部feign接口注解FeignClient信息
     *
     * @param line
     * @return
     */
    private boolean handleClassFeignAnnotationInfo(String line) {
        String[] array = line.split(Constants.FLAG_SPACE);
        if (array.length > JavaCGConstants.ANNOTATION_COLUMN_NUM_WITH_ATTRIBUTE || array.length < JavaCGConstants.ANNOTATION_COLUMN_NUM_WITHOUT_ATTRIBUTE) {
            log.error("保存注解信息文件的列数非法 {} [{}]", array.length, line);
            return false;
        }

        String classOrMethodName = array[1];
//
//        // 根据类名或完整方法前缀判断是否需要处理
//        if (!isAllowedExternalFeignClassPrefix(classOrMethodName)) {
//            return true;
//        }

        String annotationName = array[2];
        if (annotationName.endsWith(Constants.ANNOTATION_FEIGN_CLIENT)) {
            feignClientInterfaceSet.add(classOrMethodName);
            String key = array[3];
            if (Objects.equals(key, Constants.ANNOTATION_FEIGN_CLIENT_NAME)) {
                String value = array[4];
                FeignClientInfo feignClientInfo = feignClientMap.get(classOrMethodName);
                if (feignClientInfo == null) {
                    feignClientInfo = FeignClientInfo.builder().build();
                }
                feignClientInfo.setName(value);
                feignClientMap.put(classOrMethodName, feignClientInfo);

            } else if (Objects.equals(key, Constants.ANNOTATION_FEIGN_CLIENT_FALLBACK_FACTORY)) {
                String value = array[4];
                FeignClientInfo feignClientInfo = feignClientMap.get(classOrMethodName);
                if (feignClientInfo == null) {
                    feignClientInfo = FeignClientInfo.builder().build();
                }
                feignClientInfo.setFallbackFactory(value);
                feignClientMap.put(classOrMethodName, feignClientInfo);
            }
        }
        return true;
    }

    /**
     * 处理外部feign 方法注解 信息
     *
     * @param line
     * @return
     */
    private boolean handleMethodFeignAnnotationInfo(String line) {
        String[] array = line.split(Constants.FLAG_SPACE);
        if (array.length > JavaCGConstants.ANNOTATION_COLUMN_NUM_WITH_ATTRIBUTE || array.length < JavaCGConstants.ANNOTATION_COLUMN_NUM_WITHOUT_ATTRIBUTE) {
            log.error("保存注解信息文件的列数非法 {} [{}]", array.length, line);
            return false;
        }

        String classOrMethodName = array[1];

        String annotationName = array[2];

        if (annotationName.endsWith(Constants.ANNOTATION_API_OPERATION)) {
            if (array.length < 4) {
                return true;
            }
            String key = array[3];
            if (array.length < 5) {
                return true;
            }
            if (Objects.equals(key, Constants.ANNOTATION_VALUE)) {
                String value = array[4];
                AnnotationInfo annotationInfo = annotationInfoMap.get(classOrMethodName);
                if (annotationInfo == null) {
                    annotationInfo = AnnotationInfo.builder().build();
                }
                annotationInfo.setApiOperationValue(value);
                annotationInfoMap.put(classOrMethodName, annotationInfo);
            }
        } else if (annotationName.endsWith(Constants.ANNOTATION_MAPPING_END)) {
            if (array.length < 4) {
                return true;
            }
            String key = array[3];
            if (array.length < 5) {
                return true;
            }
            if (Objects.equals(key, Constants.ANNOTATION_VALUE)) {
                String value = array[4];
                AnnotationInfo annotationInfo = annotationInfoMap.get(classOrMethodName);
                if (annotationInfo == null) {
                    annotationInfo = AnnotationInfo.builder().build();
                }
                if (StringUtils.isNotEmpty(value)) {
                    annotationInfo.setMappingValue(value.substring(1, value.length() - 1));
                }
                annotationInfoMap.put(classOrMethodName, annotationInfo);
            }
        }
        return true;
    }


    /**
     * 处理一个注解信息
     *
     * @param line
     * @return
     */
    private boolean handleFeignAnnotationInfo(String line) {
        try {
            String[] array = line.split(Constants.FLAG_SPACE);
            if (array.length > JavaCGConstants.ANNOTATION_COLUMN_NUM_WITH_ATTRIBUTE
                    || array.length < JavaCGConstants.ANNOTATION_COLUMN_NUM_WITHOUT_ATTRIBUTE) {
                log.error("保存注解信息文件的列数非法 {} [{}]", array.length, line);
                return false;
            }

            String classOrMethodName = array[1];

            // 根据类名或完整方法前缀判断是否需要处理
            if (!isAllowedFeignClassPrefix(classOrMethodName)) {
                return true;
            }

            String annotationName = array[2];
            if (annotationName.endsWith(Constants.ANNOTATION_API_OPERATION)) {
                String key = array[3];
                if (Objects.equals(key, Constants.ANNOTATION_VALUE)) {
                    String value = array[4];
                    InterfaceInfo interfaceInfo = feignInterfaceList.stream()
                            .filter(feign -> Objects.equals(feign.getFullName(), classOrMethodName))
                            .findFirst()
                            .orElse(null);
                    if (interfaceInfo == null) {
                        interfaceInfo = InterfaceInfo.builder()
                                .fullName(classOrMethodName)
                                .desc(value)
                                .project(pluginContext.getComponentName())
                                .build();
                        feignInterfaceList.add(interfaceInfo);
                    }
                    interfaceInfo.setDesc(value);
                }
            } else if (annotationName.endsWith(Constants.ANNOTATION_MAPPING_END)) {
                String key = array[3];
                if (Objects.equals(key, Constants.ANNOTATION_VALUE)) {
                    String value = array[4];
                    InterfaceInfo interfaceInfo = feignInterfaceList.stream()
                            .filter(feign -> Objects.equals(feign.getFullName(), classOrMethodName))
                            .findFirst()
                            .orElse(null);
                    if (interfaceInfo == null) {
                        interfaceInfo = InterfaceInfo.builder()
                                .fullName(classOrMethodName)
                                .project(pluginContext.getComponentName())
                                .mappingPath(value.substring(1, value.length() - 1))
                                .build();
                        feignInterfaceList.add(interfaceInfo);
                    }
                    interfaceInfo.setMappingPath(value.substring(1, value.length() - 1));
                }

            }
        } catch (Exception e) {
            log.error("line: {}", line);
            log.error("e", e);
        }
        return true;
    }


    /**
     * 判断当类名为以下前缀时，才处理
     *
     * @param className 类名
     */
    private boolean isAllowedExternalFeignClassPrefix(String className) {
        for (String allowedClassPrefix : feignClientInterfaceSet) {
            if (className.startsWith(allowedClassPrefix)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断当类名为以下前缀时，才处理
     *
     * @param className 类名
     */
    private boolean isAllowedFeignClassPrefix(String className) {
        boolean result = false;
        for (String allowedClassPrefix : pluginContext.getFeignReferencePaths()) {
            if (className.startsWith(allowedClassPrefix)) {
                result = true;
            }
        }
        if (result && !PluginUtil.isCollectionEmpty(pluginContext.getExcludeFeignApiReferencePaths())) {
            for (String allowedClassPrefix : pluginContext.getExcludeFeignApiReferencePaths()) {
                if (className.startsWith(allowedClassPrefix)) {
                    result = false;
                }
            }
        }
        return result;
    }

    /**
     * 删除java-callgraph2生成的直接调用关系文件
     */
    private void doDelete() {
        if (pluginContext.getDeleteMergeFile() != null && !pluginContext.getDeleteMergeFile()) {
            return;
        }
        if (StringUtils.isNotEmpty(callGraphOutputFilePath)) {
            new File(callGraphOutputFilePath.replace(Constants.EXT_TXT, "")).delete();
            new File(callGraphOutputFilePath).delete();
        }
        if (StringUtils.isNotEmpty(callGraphAnnotationOutputFilePath)) {
            new File(callGraphAnnotationOutputFilePath).delete();
        }
        if (StringUtils.isNotEmpty(callGraphLineNumberOutputFilePath)) {
            new File(callGraphLineNumberOutputFilePath).delete();
        }
        if(StringUtils.isNotEmpty(dependJarPath)){
            new File(dependJarPath).delete();
        }
    }


    public List<String> getJarPath(File module) {
        if (Constants.IGNORE_JAR_PATH.contains(module.getName())) {
            return new ArrayList<>();
        }
        File targetFile = null;
        File[] moduleChilds = {};
        if (Objects.equals(Constants.TARGET, module.getName())) {
            targetFile = module;
        } else {
            moduleChilds = module.listFiles();
            if (moduleChilds == null) {
                return new ArrayList<>();
            }
            targetFile = Arrays.asList(moduleChilds).stream()
                    .filter(moduleChild ->
                            moduleChild.getName().equals(Constants.TARGET))
                    .findFirst().orElse(null);
        }

        if (targetFile != null) {
            File zipFile = Arrays.asList(targetFile.listFiles()).stream()
                    .filter(moduleChild ->
                            moduleChild.getName().endsWith(Constants.ZIP))
                    .findFirst().orElse(null);
            if (zipFile != null) {
                zipPath = zipFile.getAbsolutePath();
                log.info("zipPath : {}", zipFile);
            }

            File singleJarFile = Arrays.asList(targetFile.listFiles()).stream()
                    .filter(moduleChild ->
                            moduleChild.getName().endsWith(Constants.SINGLE_JAR))
                    .findFirst().orElse(null);
            if (singleJarFile != null) {
                return Arrays.asList(singleJarFile.getAbsolutePath());
            }
            File jarFile = Arrays.asList(targetFile.listFiles()).stream()
                    .filter(moduleChild -> {
                        boolean result = moduleChild.getName().endsWith(Constants.JAR)
                                && !moduleChild.getName().endsWith(Constants.JAR_FILTER_SOURCES)
                                && !moduleChild.getName().endsWith(Constants.JAR_JAVACG_MERGED_JAR)
                                && !moduleChild.getName().endsWith(Constants.JAR_FILTER_ENTITY)
                                && !moduleChild.getName().contains(Constants.JAR_FILTER_POJO);
                        if (!PluginUtil.isCollectionEmpty(pluginContext.getExcludeJarFuzzyNames())) {
                            for (String excludeJarFuzzyName : pluginContext.getExcludeJarFuzzyNames()) {
                                if (moduleChild.getName().contains(excludeJarFuzzyName)) {
                                    result = false;
                                }
                            }
                        }
                        return result;
                    }).findFirst().orElse(null);
            if (jarFile != null) {
                return Arrays.asList(jarFile.getAbsolutePath());
            }
        }

        List<String> jarPaths = new ArrayList<>();
        for (File moduleChild : moduleChilds) {
            jarPaths.addAll(getJarPath(moduleChild));
        }
        return jarPaths;
    }
}
